﻿/*******************************************************************
 *     JameE
 *******************************************************************
  Copyright 2010 Clifford Champion and JameE contributors.
  
  This file is part of JameE.
  
  JameE is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  JameE is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with JameE.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************/
 
 
/// <reference path="~/Base/Classer.js" />
/// <reference path="~/Base/IncludeLoader.js" />

$include(
[
	"Math/Graph.js",
	"Math/Vector3.js",
	"Math/Rectangle.js",
	"Math/Matrix.js",

	"Utils/Array.js",

	"Engine/ISceneObject.js",
	"Engine/SceneObject.js",
	"Engine/Scene.js",

	"Assets/Shared.js",

	"Engine/PostOffice.js",
	"Engine/Message.js",
	"Engine/BroadPhase.js",
	"Engine/Image.js",
	"Engine/Aabb.js",
	"Engine/PathFinding.js"
],
function () {

	$class(
	"JameE.Engine.Engine",
	{
		/////////////////////
		// CONSTANTS
		/////////////


		/////////////////////
		// FIELDS
		/////////////
		_oScene: null,
		_fFrameRate: null,

		_oCanvas: null,
		_oGraphicsContext: null,

		_oSetIntervalHandle: null,
		_iCurrentTime: 0, // game time, in milliseconds


		/////////////////////
		// CONSTRUCTOR
		/////////////
		_ctor: function (args) {
			this._fFrameRate = $argument("frameRate", "number") || 40.0;
		},


		/////////////////////
		// METHODS: setup
		/////////////
		setCanvas: function (oCanvas) {
			if (oCanvas == null) {
				throw new Error("Expected a Canvas.");
			}
			this._oCanvas = oCanvas;
			this._oGraphicsContext = oCanvas.getContext("2d");

			// is this an emulated canvas with missing pieces?
			if (typeof (this._oGraphicsContext.fillText) === "undefined") {
				this._oGraphicsContext.fillText = function () { };
			}

			this._bindInputListeners();
		},

		setScene: function (oScene) {
			$require_type(oScene, "JameE.Engine.Scene");
			this._oScene = oScene;
		},


		/////////////////////
		// METHODS: game loop
		/////////////
		run: function () {
			var This = this;

			if (this._oSetIntervalHandle != null) {
				return; // already running
			}

			// calculate delay between frames
			var iFrameDuration = 1 / this._fFrameRate * 1000;

			this._iCurrentTime = new Date().getTime();

			// setup timer
			this._oSetIntervalHandle
				= window.setInterval(
					function () {
						This._loop();
					},
					iFrameDuration
				)
			;
		},

		pause: function () {
			window.clearInterval(this._oSetIntervalHandle);
			this._oSetIntervalHandle = null;
		},

		_loop: function () {
			// calculate new time
			var iNowMs = new Date().getTime();
			var iDeltaMs = iNowMs - this._iCurrentTime;
			this._iCurrentTime = iNowMs;

			// update state
			this.update(iDeltaMs);

			// draw
			this.draw();
		},

		update: function (iDeltaMs) {
			if (this._oScene != null) {
				this._oScene.update(iDeltaMs, this._iCurrentTime);
			}
		},

		draw: function () {
			if (this._oScene != null && this._oGraphicsContext != null) {

				// transform to assume normalized coordinate space
				this._oGraphicsContext.setTransform(
					this._oGraphicsContext.canvas.width / 2.0,
					0.0,
					0.0,
					this._oGraphicsContext.canvas.height / 2.0,
					this._oGraphicsContext.canvas.width / 2.0,
					this._oGraphicsContext.canvas.height / 2.0
				);

				this._oScene.draw(this._oGraphicsContext);
			}
		},


		/////////////////////
		// METHODS: queries
		/////////////
		getTime: function () {
			return this._iCurrentTime;
		},


		/////////////////////
		// METHODS: communication
		/////////////
		message: function (oMessage) {

			// send to current scene
			if (this._oScene) {
				this._oScene.message(oMessage);
			}
		},


		/////////////////////
		// METHODS: input
		/////////////
		_bindInputListeners: function () {
			var This = this;

			var oCanvas = this._oCanvas;

			if (oCanvas === null) {
				return;
			}

			// todo: translate mouse coordinates into canvas-local logical.

			// DOM 2
			if (oCanvas.addEventListener) {

				// mouse
				oCanvas.addEventListener(
					"mousemove",
					function (oEvent) { This._onMouseMove(oEvent); },
					false
				);
				oCanvas.addEventListener(
					"click",
					function (oEvent) { This._onMouseClick(oEvent); },
					false
				);
				oCanvas.addEventListener(
					"mousedown",
					function (oEvent) { This._onMouseDown(oEvent); },
					false
				);
				oCanvas.addEventListener(
					"mouseup",
					function (oEvent) { This._onMouseUp(oEvent); },
					false
				);

				// keyboard
				document.addEventListener(
					"keydown",
					function (oEvent) { This._onKeyDown(oEvent); },
					false
				);
				document.addEventListener(
					"keyup",
					function (oEvent) { This._onKeyUp(oEvent); },
					false
				);
			}
			// IE
			else {

				// mouse
				oCanvas.attachEvent(
					"onmousemove",
					function (oEvent) { This._onMouseMove(oEvent); }
				);
				oCanvas.attachEvent(
					"onclick",
					function (oEvent) { This._onMouseClick(oEvent); }
				);
				oCanvas.attachEvent(
					"onmousedown",
					function (oEvent) { This._onMouseDown(oEvent); }
				);
				oCanvas.attachEvent(
					"onmouseup",
					function (oEvent) { This._onMouseUp(oEvent); }
				);

				// keyboard
				document.attachEvent(
					"onkeydown",
					function (oEvent) { This._onKeyDown(oEvent); }
				);
				document.attachEvent(
					"onkeyup",
					function (oEvent) { This._onKeyUp(oEvent); }
				);
			}
		},

		_onMouseMove: function (oEvent) {

			var fX, fY;
			fX = oEvent.clientX;
			fY = oEvent.clientY;

			var oPoint_CanvasLocal = this._mapViewportCoordsToLogicalCoords(fX, fY);
			var oPoint_CanvasNormalized = this._mapLogicalCoordsToNormalizedCoords(oPoint_CanvasLocal.fX, oPoint_CanvasLocal.fY);
			var oMessage = new JameE.Engine.Message(
				{
					message: JameE.Engine.MessageTypes.MOUSE_MOVE_RAW,
					data: { x: oPoint_CanvasNormalized.fX, y: oPoint_CanvasNormalized.fY }
				}
			);
			this.message(oMessage);
		},

		_onMouseClick: function (oEvent) {

			var fX, fY;
			fX = oEvent.clientX;
			fY = oEvent.clientY;

			var oPoint_CanvasLocal = this._mapViewportCoordsToLogicalCoords(fX, fY);
			var oPoint_CanvasNormalized = this._mapLogicalCoordsToNormalizedCoords(oPoint_CanvasLocal.fX, oPoint_CanvasLocal.fY);

			var oMessage = new JameE.Engine.Message(
				{
					message: JameE.Engine.MessageTypes.MOUSE_CLICK_RAW,
					data: { x: oPoint_CanvasNormalized.fX, y: oPoint_CanvasNormalized.fY }
				}
			);
			this.message(oMessage);
		},

		_onMouseDown: function (oEvent) {
			var fX, fY;
			fX = oEvent.clientX;
			fY = oEvent.clientY;

			var oPoint_CanvasLocal = this._mapViewportCoordsToLogicalCoords(fX, fY);
			var oPoint_CanvasNormalized = this._mapLogicalCoordsToNormalizedCoords(oPoint_CanvasLocal.fX, oPoint_CanvasLocal.fY);

			var oMessage = new JameE.Engine.Message(
				{
					message: JameE.Engine.MessageTypes.MOUSE_DOWN_RAW,
					data: { x: oPoint_CanvasNormalized.fX, y: oPoint_CanvasNormalized.fY }
				}
			);
			this.message(oMessage);
		},

		_onMouseUp: function (oEvent) {
			var fX, fY;
			fX = oEvent.clientX;
			fY = oEvent.clientY;

			var oPoint_CanvasLocal = this._mapViewportCoordsToLogicalCoords(fX, fY);
			var oPoint_CanvasNormalized = this._mapLogicalCoordsToNormalizedCoords(oPoint_CanvasLocal.fX, oPoint_CanvasLocal.fY);

			var oMessage = new JameE.Engine.Message(
				{
					message: JameE.Engine.MessageTypes.MOUSE_UP_RAW,
					data: { x: oPoint_CanvasNormalized.fX, y: oPoint_CanvasNormalized.fY }
				}
			);
			this.message(oMessage);
		},

		_onKeyDown: function (oEvent) {
			var oMessage = new JameE.Engine.Message(
				{
					message: JameE.Engine.MessageTypes.KEY_DOWN,
					data: { key: oEvent.keyCode }
				}
			);
			this.message(oMessage);
		},

		_onKeyUp: function (oEvent) {
			var oMessage = new JameE.Engine.Message(
				{
					message: JameE.Engine.MessageTypes.KEY_UP,
					data: { key: oEvent.keyCode }
				}
			);
			this.message(oMessage);
		},

		_mapLogicalCoordsToNormalizedCoords: function (fX, fY) {
			var fWidthHalf = this._oCanvas.width / 2.0;
			var fHeightHalf = this._oCanvas.height / 2.0;
			fX = (fX - fWidthHalf) / fWidthHalf;
			fY = (fY - fHeightHalf) / fHeightHalf;
			return { fX: fX, fY: fY };
		},

		_mapViewportCoordsToLogicalCoords: function (fX, fY) {
			var oCanvasBoundingRect = this._oCanvas.getBoundingClientRect();
			fX = fX - oCanvasBoundingRect.left;
			fY = fY - oCanvasBoundingRect.top;
			return { fX: fX, fY: fY };
		},

		_mapScreenCoordsToViewportCoords: function (fX, fY) {
			var fViewportX = window.screenLeft || window.screenX;
			var fViewportY = window.screenTop || window.screenY;

			fX = fX - fViewportX;
			fY = fY - fViewportY;

			return { fX: fX, fY: fY };
		}
	});

	$include_notify("Engine/Engine.js");
});